package org.raymond.iworks.study.basic.thread.phaser;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Phaser;

@Slf4j
public class Demo3 {
    public static void main(String[] args) {
        test1();
    }
    /**
     * 当Phaser的parties数比较大的高并发场景下,Phaser的status变量的竞争会非常激烈,
     * register,arrive等操作发起的CAS操作预测将会大概率失败导致大量CAS操作被重复调用,增加CPU开销.
     * 可以通过构造Phaser分层树的方式来分离竞争,子Phaser第一次register时,把该子Phaser注册到父Phaser,
     * 当子Phaser所有parties都已经arrive时,把它从父Phaser中取消注册
     *
     * 当根Phaser的所有子Phaser的parties都已经arrive时,整个Phaser树升级phase递增,
     * 通过这种方式,所有的arrive,register操作在子Phaser进行就可以,根Phaser只需负责Phaser的升级,
     * 这样可以把部分对status的访问修改分离到子Phaser中,通过分散竞争点提高Phaser的吞吐量
     *
     * 下面示例代码就把9个parties分散到了两个子Phaser中:
     * 注册parties时,主要做的事情就是修改status变量的parties部分和unarrive部分,
     * 同时也可以看到.在第一次注册时,调用了代码parent.doRegister(1),注册一次party到父Phaser.
     * 线程触发arrive操作时,如果当前Phaser的所有parties都已经arrive,
     * 那么调用一次parent.doArrive(1),如果所有parties都已经arrive并且当前Phaser时根Phaser,
     * 说明该Phaser可以升级,phase值加1,唤醒由于调用了awaitXXX被阻塞的线程.在升级时有个onAdvance回调可以让调用者终止Phaser.
     * 触发Phaser的awaitAdvance时,Phaser的await操作不会直接挂起线程,会先对根Phaser的status自旋检查,检查phase是否发生了变化,
     * 自旋了若干次(这个数值跟当前CPU的核心数有关)之后如果phase还未发生变化则挂起线程,这样做的目的是挂起线程会造成上下文切换,
     * 如果Phaser在很短的时间内就升级了,那么这样就减少了上下文切换次数提高CPU吞吐量,但是自旋检查也会造成CPU消耗,所以也不能一直自旋.
     * 在上面的arrive方法中可以看到,当所有parties都arrive之后修改phase值加1,所以internalAwaitAdvance方法中的while条件将不成立,
     * 跳出循环唤醒所有等待的线程.下面是唤醒线程的方法:releaseWaiters
     *
     * 这个方法相对简单,就是有一点需要注意的是:有两个线程等待队列头节点分别是evenQ和oddQ,这是因为在并发场景下,
     * 老Phaser所有parties都已经arrive之后等待队列的线程正在被唤醒,但是此时又有线程在对升级后的Phaser调用了await,
     * 如果只有一个队列的话那么此时队列头结点出现激烈的竞争，所以这里面把相邻的年龄的Phaser等待线程放在两个队列中可以达到分离竞争的目的
     */
    public static void test1(){
        Phaser parent = new Phaser();
        Phaser phaser1 = new Phaser(parent);
        Phaser phaser2 = new Phaser(parent);
        phaser1.bulkRegister(5);
        phaser2.bulkRegister(4);
        for(int i=0; i<5; i++){
            new Thread(()->{
                phaser1.arriveAndAwaitAdvance();
            }).start();
        }
        for(int i=0; i<5; i++){
            final int j = i;
            new Thread(()->{
                log.info("arrive {}", j);
                phaser2.arriveAndAwaitAdvance();
            });
        }
        parent.awaitAdvance(parent.getPhase());
    }
}
